package com.example.diancai.ai.game2048;

import com.alibaba.fastjson2.JSON;
import com.example.diancai.dto.Vec3DTO;
import com.example.diancai.websocket.dto.UsePropDTO;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

public class Ai2048 {
    public Grid grid;


    public Ai2048(Grid grid) {
        this.grid = grid.clone();

    }

    public int eval() {
        List<Vec3DTO> availableCells = this.grid.availableCells();
        int emptyCells = availableCells.size();

        double smoothWeight = 0.1;


        double mono2Weight = 1.0;
        double emptyWeight = 2.7;
        double maxWeight = 1.0;

        return (int) (this.grid.smoothness() * smoothWeight
                + this.grid.monotonicity2() * mono2Weight
                + Math.log(emptyCells) * emptyWeight
                + this.grid.maxValue() * maxWeight);
    }

    public SearchResult search(int depth, int alpha, int beta, int positions, int cutoffs) {
        int bestScore;
        Integer bestMove = -1;
        SearchResult result;
        if (this.grid.playerTurn) {
            bestScore = alpha;
            for (int direction = 0; direction < 4; direction++) {
                Grid newGrid = this.grid.clone();
                MoveResult moveResult = newGrid.move(direction);

                if (moveResult.moved) {


                    positions++;
                 /*   if (newGrid.isWin()) {
                        return new SearchResult(direction, 10000, positions, cutoffs);
                    }*/

                    Ai2048 newAI = new Ai2048(newGrid);
                    if (depth == 0) {

                        result = new SearchResult(direction, newAI.eval(), positions, cutoffs);
                    } else {
                        result = newAI.search(depth - 1, bestScore, beta, positions, cutoffs);
                        if (result.score > 9900) {
                            result.score--;
                        }
                    }

                    if (result.score > bestScore) {
                        bestScore = result.score;
                        bestMove = direction;
                    }
                    if (bestScore > beta) {

                        cutoffs++;
                        return new SearchResult(bestMove, beta, positions, cutoffs);
                    }
                }
            }
        } else { // computer's turn, we'll do heavy pruning to keep the branching factor low
            bestScore = beta;

            // try a 2 and 4 in each cell and measure how annoying it is
            // with metrics from eval
            List<CellCandidate> candidates = new ArrayList<>();
            List<Vec3DTO> cells = this.grid.availableCells();
            List<Integer> scores2 = new ArrayList<>();
            List<Integer> scores4 = new ArrayList<>();

            for (int i = 0; i < cells.size(); i++) {
                scores2.add(0);
                scores4.add(0);
            }

            for (int value : new int[]{2, 4}) {
                for (int i = 0; i < cells.size(); i++) {
                    Vec3DTO cell = cells.get(i);
                    cell.setZ(value);
                    Tile tile = new Tile(cell);
                    this.grid.insertTile(tile);
                    if (value == 2) {
                        scores2.set(i, (int) (-this.grid.smoothness() + this.grid.islands()));
                    } else {
                        scores4.set(i, (int) (-this.grid.smoothness() + this.grid.islands()));
                    }
                    this.grid.removeTile(cell);
                }
            }

            // now just pick out the most annoying moves
            int maxScore2 = scores2.stream().mapToInt(Integer::intValue).max().orElse(0);
            int maxScore4 = scores4.stream().mapToInt(Integer::intValue).max().orElse(0);
            int maxScore = Math.max(maxScore2, maxScore4);

            for (int value : new int[]{2, 4}) {
                List<Integer> scores = (value == 2) ? scores2 : scores4;
                for (int i = 0; i < scores.size(); i++) {
                    if (scores.get(i) == maxScore) {
                        candidates.add(new CellCandidate(cells.get(i), value));
                    }
                }
            }

            // search on each candidate
            for (CellCandidate candidate : candidates) {
                Vec3DTO position = candidate.position;
                int value = candidate.value;
                Grid newGrid = this.grid.clone();
                position.setZ(value);
                Tile tile = new Tile(position);
                newGrid.insertTile(tile);
                newGrid.playerTurn = true;
                positions++;
                Ai2048 newAI = new Ai2048(newGrid);
                result = newAI.search(depth, alpha, bestScore, positions, cutoffs);
                positions = result.positions;
                cutoffs = result.cutoffs;

                if (result.score < bestScore) {
                    bestScore = result.score;
                }
                if (bestScore < alpha) {
                    cutoffs++;
                    return new SearchResult(null, alpha, positions, cutoffs);
                }
            }
        }

        return new SearchResult(bestMove, bestScore, positions, cutoffs);
    }

    public SearchResult getBest() {
        return this.iterativeDeep();
    }

    private SearchResult iterativeDeep() {
        long start = System.currentTimeMillis();
        int depth = 0;
        SearchResult best = null;

        do {
            SearchResult newBest = this.search(depth, -10000, 10000, 0, 0);
            if (newBest.move == -1) {
                break;
            }
            best = newBest;
            depth++;
        } while (System.currentTimeMillis() - start < grid.getMinSearchTime());
        System.out.println("最小搜索时间: " + grid.getMinSearchTime());
        return best;
    }

    public String translate(int move) {
        String[] directions = {"up", "right", "down", "left"};
        if (move >= 0 && move < 4) {
            return directions[move];
        }
        return null;
    }


}
